home *** CD-ROM | disk | FTP | other *** search
/ SuperHack / SuperHack CD.bin / CODING / DELPHI / SWEEPGEN.ZIP / MAINFORM.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1996-06-01  |  20.7 KB  |  648 lines

  1. unit MainForm;
  2.  
  3. {
  4. SweepGen - David's Audio Sweep Generator
  5.  
  6. Revision History
  7.  
  8. V0.0     1994 Oct 09  First version, combining SloSweep and Sinewave
  9. V0.0-01  1994 Oct 10  Use TDlgWindow as main window
  10.                       Move sweep_running to main data segment
  11. V0.0-02  1994 Oct 12  Get double-buffering working properly
  12.                       Put sweep_running back in object data!
  13. V1.0.0   1995 May 07  Version for Delphi 1.0
  14. V1.1.0   1995 Oct 08  Better quality, 16-bit audio
  15. V2.0.0   1996 Jun 01  Version for 32-bit Delphi
  16.                       Add more output levels
  17.                       Allow for smooth or stepped fast sweep
  18.                       Improve generation to about 15-bit accuracy
  19.                       Release to public domain
  20. }
  21.  
  22. interface
  23.  
  24. {$A-}
  25. {$D David's Audio Sweep Generator ⌐ David J Taylor, Edinburgh, 1994-1996}
  26.  
  27. uses
  28.   SysUtils, Windows, Messages, Classes, Graphics, Controls,
  29.   Forms, Dialogs, StdCtrls, ExtCtrls, MMSystem, mmErrMsg;
  30.  
  31. const
  32.   sweep_time = 45;                   // seconds for slow sweep
  33.   sample_rate = 44100;               // i.e. best CD quality
  34.   sine_table_samples = 1 shl 15;     // number of samples in sine table
  35.   max_buffer_samples = 32000;        // reasonable size of output buffer (< 64K)
  36.   open_error = 'Error opening waveform audio!';
  37.   mem_error = 'Error allocating memory!';
  38.  
  39. type
  40.   audio_sample = -32767..32767;       // for 16-bit audio
  41.  
  42. type
  43.   PSineTable = ^TSineTable;          // sine value store
  44.   TSineTable = array [0..sine_table_samples-1] of audio_sample;
  45.  
  46.   PBuffer = ^TBuffer;                // output buffer type
  47.   TBuffer = array [0..max_buffer_samples-1] of audio_sample;
  48.  
  49.   levels = (dB0, dB3, dB6, dB9, dB12, dB15, dB18, dB20);  // output levels
  50.   ranges = (lf, mf, hf, wide);                            // sweep ranges
  51.   modes = (logarithmic, linear);                          // sweep modes
  52.   speeds = (fast_stepped, fast_smooth, slow, no_sweep);   // sweep speeds
  53.  
  54.  
  55. type
  56.   TForm1 = class(TForm)
  57.     Panel1: TPanel;
  58.     Panel2: TPanel;
  59.     btnExit: TButton;
  60.     grpFrequencyRange: TRadioGroup;
  61.     btnStart: TButton;
  62.     grpSweepMode: TRadioGroup;
  63.     grpSweepSpeed: TRadioGroup;
  64.     grpOutputLevel: TRadioGroup;
  65.     edtF1: TEdit;
  66.     Label1: TLabel;
  67.     edtF2: TEdit;
  68.     Label2: TLabel;
  69.     lblFnow: TLabel;
  70.     procedure btnExitClick(Sender: TObject);
  71.     procedure FormCreate(Sender: TObject);
  72.     procedure grpSweepModeClick(Sender: TObject);
  73.     procedure grpOutputLevelClick(Sender: TObject);
  74.     procedure grpSweepSpeedClick(Sender: TObject);
  75.     procedure grpFrequencyRangeClick(Sender: TObject);
  76.     procedure FormDestroy(Sender: TObject);
  77.     procedure btnStartClick(Sender: TObject);
  78.     procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
  79.   private
  80.     { Private declarations }
  81.     angle: integer;          // current sine wave angle
  82.     sine_table: PSineTable;  // sine-wave values are pre-stored in this array
  83.     p_wave_hdr1: PWaveHdr;   // wave headers
  84.     p_wave_hdr2: PWaveHdr;
  85.     p_buffer1: PBuffer;      // output buffers
  86.     p_buffer2: PBuffer;
  87.     hWave_hdr1: HGlobal;
  88.     hWave_hdr2: HGlobal;
  89.     hBuffer1: HGlobal;
  90.     hBuffer2: HGlobal;
  91.     buffer_bytes: integer;   // max number of bytes in each output buffer
  92.     f_min, f_max: integer;   // limits of sweep range
  93.     buffers_written, buffers_played: integer;  // for tracking the slow sweep
  94.     all_written: boolean;    // so we know when to stop the sweep
  95.     f, f_ratio, f_step, last_f: extended;
  96.     hWave_out: HWaveOut;     // handle to wave out device
  97.     pcm: TWaveFormatEx;      // wave format descriptor
  98.     sweep_running: boolean;
  99.     shutoff: boolean;
  100.     closing: boolean;
  101.     sine_table_done: boolean;
  102.     closed: boolean;
  103.     level: levels;
  104.     log_lin: modes;
  105.     speed: speeds;
  106.     range: ranges;
  107.     procedure restart_sweep;
  108.     procedure stop_sweep;
  109.     procedure start_sweep;
  110.  
  111.     // call-backs from waveform out functions
  112.     procedure mm_wom_Open (var Msg: TMessage);  message mm_wom_open;
  113.     procedure mm_wom_Done (var Msg: TMessage);  message mm_wom_done;
  114.     procedure mm_wom_Close (var Msg: TMessage);  message mm_wom_close;
  115.  
  116.     function fill_single_sweep_bfr (bfr: PBuffer;  num_freqs: integer): integer;
  117.     procedure fill_buffer_with_sinewave (bfr: PBuffer;  index, samples: integer);
  118.     procedure write_next_buffer (header: PWaveHdr);
  119.     procedure do_sine_table;
  120.  
  121.   public
  122.     { Public declarations }
  123.   end;
  124.  
  125. var
  126.   Form1: TForm1;
  127.  
  128. implementation
  129.  
  130. {$R *.DFM}
  131. {$R version.res}
  132.  
  133. procedure TForm1.FormCreate(Sender: TObject);
  134. begin
  135.   // set the default positions for the RadioGroup boxes, this forces the
  136.   // dependant variables and the label captions to be set
  137.   grpOutputLevel.ItemIndex := 4;
  138.   grpSweepMode.ItemIndex := 1;
  139.   grpFrequencyRange.ItemIndex := 2;
  140.   grpSweepSpeed.ItemIndex := 2;
  141.  
  142.   // get the memory required for wave headers
  143.   // this code is probably irrelevant in the Win32 environment
  144.   hWave_hdr1 := GlobalAlloc (gHnd or gMem_Share, SizeOf (TWaveHdr));
  145.   p_wave_hdr1 := pWaveHdr (GlobalLock (hWave_hdr1));
  146.   hWave_hdr2 := GlobalAlloc (gHnd or gMem_Share, SizeOf (TWaveHdr));
  147.   p_wave_hdr2 := pWaveHdr (GlobalLock (hWave_hdr2));
  148.  
  149.   // estimate of reasonable output buffer size
  150.   buffer_bytes := 2 * round (1.2 * sample_rate);
  151.   if buffer_bytes > 2 * max_buffer_samples
  152.     then buffer_bytes := 2 * max_buffer_samples;
  153.  
  154.   // get the memory required for output buffers
  155.   hBuffer1 := GlobalAlloc (gHnd or gMem_Share, buffer_bytes);
  156.   p_buffer1 := pBuffer (GlobalLock (hBuffer1));
  157.   hBuffer2 := GlobalAlloc (gHnd or gMem_Share, buffer_bytes);
  158.   p_buffer2 := pBuffer (GlobalLock (hBuffer2));
  159.  
  160.   hWave_out := 0;
  161.   // get the memory for the sine-wave table and note it hasn't been built, yet
  162.   GetMem (sine_table, SizeOf (TSineTable));
  163.   sine_table_done := false;
  164.  
  165.   // set other state variables
  166.   shutoff := false;
  167.   closing := false;
  168.   sweep_running := false;
  169. end;
  170.  
  171.  
  172. procedure TForm1.FormDestroy(Sender: TObject);
  173. begin
  174.   shutoff := true;
  175.   GlobalUnlock (hWave_hdr1);  GlobalFree (hWave_hdr1);
  176.   GlobalUnlock (hBuffer1);  GlobalFree (hBuffer1);
  177.   GlobalUnlock (hWave_hdr2);  GlobalFree (hWave_hdr2);
  178.   GlobalUnlock (hBuffer2);  GlobalFree (hBuffer2);
  179.   FreeMem (sine_table, SizeOf (TSineTable));
  180. end;
  181.  
  182.  
  183. procedure TForm1.btnExitClick(Sender: TObject);
  184. begin
  185.   Close;
  186. end;
  187.  
  188.  
  189. procedure TForm1.grpSweepModeClick(Sender: TObject);
  190. // This is typical of the code for all the RadioGroups.  Find
  191. // the current string and decode it.  Set a label caption equal
  192. // to the decoded value, often just the current string
  193. var
  194.   current: string;
  195. begin
  196.   current := grpSweepMode.Items.Strings [grpSweepMode.ItemIndex];
  197.   if current = 'Linear' then log_lin := linear;
  198.   if current = 'Log' then log_lin := logarithmic;
  199.   lblFnow.Caption := LowerCase (current);
  200.   // the sweep parameters have changed, so restart any sweep in progress
  201.   restart_sweep;
  202. end;
  203.  
  204.  
  205. procedure TForm1.grpOutputLevelClick(Sender: TObject);
  206. var
  207.   current: string;
  208. begin
  209.   current := grpOutputLevel.Items.Strings [grpOutputLevel.ItemIndex];
  210.   if current = '0dB' then level := dB0;
  211.   if current = '-3dB' then level := dB3;
  212.   if current = '-6dB' then level := dB6;
  213.   if current = '-9dB' then level := dB9;
  214.   if current = '-12dB' then level := dB12;
  215.   if current = '-15dB' then level := dB15;
  216.   if current = '-18dB' then level := dB18;
  217.   if current = '-20dB' then level := dB20;
  218.   lblFnow.Caption := current;
  219.   sine_table_done := false;   // level is different, so throw away present table
  220.   restart_sweep;
  221. end;
  222.  
  223.  
  224. procedure TForm1.grpSweepSpeedClick(Sender: TObject);
  225. var
  226.   current: string;
  227. begin
  228.   current := grpSweepSpeed.Items.Strings [grpSweepSpeed.ItemIndex];
  229.   if current = 'Slow' then speed := slow;
  230.   if current = 'Fast (stepped)' then speed := fast_stepped;
  231.   if current = 'Fast (smooth)' then speed := fast_smooth;
  232.   if current = 'No sweep' then speed := no_sweep;
  233.   case speed of
  234.     slow, fast_stepped, fast_smooth: edtF2.Visible := True;
  235.     no_sweep: edtF2.Visible := False;
  236.   end;
  237.   lblFnow.Caption := LowerCase (current);
  238.   restart_sweep;
  239. end;
  240.  
  241.  
  242. procedure TForm1.grpFrequencyRangeClick(Sender: TObject);
  243. var
  244.   f1, f2: integer;
  245.   current: string;
  246. begin
  247.   current := grpFrequencyRange.Items.Strings [grpFrequencyRange.ItemIndex];
  248.   if current = 'Wide  (20Hz .. 20KHz)' then range := wide;
  249.   if current = 'HF  (1KHz .. 15KHz)' then range := hf;
  250.   if current = 'Speech  (300Hz .. 3KHz)' then range := mf;
  251.   if current = 'LF  (50Hz .. 1KHz)' then range := lf;
  252.   case range of
  253.       lf: begin
  254.           f1 := 50;  f2 := 1000;
  255.           end;
  256.       mf: begin
  257.           f1 := 300;  f2 := 3000;
  258.           end;
  259.       hf: begin
  260.           f1 := 1000;  f2 := 15000;
  261.           end;
  262.     wide: begin
  263.           f1 := 20;  f2 := 20000;
  264.           end;
  265.     else
  266.           begin
  267.           f1 := 300;  f2 := 3000;
  268.           end;
  269.   end;
  270.   // record the new frequency range in the Edit boxes
  271.   edtF1.Text := IntToStr (f1);
  272.   edtF2.Text := IntToStr (f2);
  273.   case range of
  274.     lf: lblFnow.Caption := 'lf';
  275.     mf: lblFnow.Caption := 'mf';
  276.     hf: lblFnow.Caption := 'hf';
  277.     wide: lblFnow.Caption := 'wide';
  278.   end;
  279.   restart_sweep;
  280. end;
  281.  
  282.  
  283. procedure TForm1.restart_sweep;
  284. begin
  285.   if sweep_running then start_sweep;
  286. end;
  287.  
  288.  
  289. procedure TForm1.stop_sweep;
  290. begin
  291.   // is a sweep running?  if so, stop it
  292.   if sweep_running
  293.   then
  294.     begin
  295.     shutoff := true;
  296.     waveOutReset (hWave_out);
  297.     sweep_running := false;
  298.     closed := false;
  299.     repeat
  300.       Application.ProcessMessages;
  301.     until closed;
  302.     end
  303. end;
  304.  
  305.  
  306. procedure TForm1.start_sweep;
  307. var
  308.   open_status: MMRESULT;
  309.   code: integer;
  310. begin
  311.   if sweep_running then stop_sweep;
  312.  
  313.   // try to convert the text in the edit boxes to numbers
  314.   Val (edtF1.Text, f_min, code);
  315.   if code <> 0 then f_min := 150;
  316.   Val (edtF2.Text, f_max, code);
  317.   if code <> 0 then f_max := 300;
  318.  
  319.   angle := 0;
  320.   // fill in the TWaveFormatEx structure with our wave details
  321.   with pcm do
  322.     begin
  323.     wFormatTag := wave_Format_PCM;         // it's PCM data
  324.     nChannels := 1;                        // mono
  325.     nSamplesPerSec := sample_rate;         // set the 44.1KHz rate
  326.     nAvgBytesPerSec := 2 * sample_rate;    // two bytes per sample
  327.     nBlockAlign := 2;                      // for mono 16-bit audio
  328.     wBitsPerSample := 16;                  // 16-bit audio
  329.     cbSize := 0;
  330.     end;
  331.  
  332.   shutoff := false;
  333.   // try and open the wave device for our format of wave data
  334.   open_status := waveOutOpen (@hWave_out, 0, @pcm, Handle, 0, callback_window);
  335.  
  336.   if open_status = 0
  337.   then
  338.     begin
  339.     // prepare to receive the WaveOutOpen message to sctually start sending data
  340.     sweep_running := true;
  341.     closed := false;
  342.     if (speed = slow) or (speed = no_sweep) then
  343.       begin
  344.       lblFnow.Caption := IntToStr (f_min) + ' Hz';
  345.       lblFnow.Visible := True;
  346.       end;
  347.     end
  348.   else
  349.     begin
  350.     sweep_running := false;
  351.     hWave_out := 0;
  352.     // inform user of failure
  353.     MessageDlg (open_error + #13#10 + translate_mm_error (open_status),
  354.                 mtWarning, [mbOK], 0);
  355.     end;
  356. end;
  357.  
  358.  
  359. procedure TForm1.btnStartClick(Sender: TObject);
  360. begin
  361.   {is a sweep running?  if so, stop it}
  362.   if sweep_running
  363.   then stop_sweep
  364.   else start_sweep;
  365. end;
  366.  
  367.  
  368. procedure TForm1.mm_wom_open (var Msg: tMessage);
  369. // This code handles the WaveOutOpen message by writing two buffers of data
  370. // to the wave device.  Plus other miscellaneous housekeeping.
  371. var
  372.    chunks: integer;
  373.    buffer_fill: integer;
  374.    samples: integer;             // max valid sample in the buffer
  375. begin
  376.   btnStart.Caption := 'STOP';    // first, tell the user how to stop the sound!
  377.  
  378.   if not sine_table_done then do_sine_table;  // build sine-wave table if required
  379.  
  380.   // populate the first wave header
  381.   with p_wave_hdr1^ do
  382.     begin
  383.     lpData := pChar (p_buffer1);   // pointer to the data
  384.     dwBufferLength := 0;           // fill in size later
  385.     dwBytesRecorded := 0;
  386.     dwUser := 0;
  387.     dwFlags := 0;
  388.     dwLoops := 1;                  // just a single loop
  389.     lpNext := nil;
  390.     reserved := 0;
  391.     end;
  392.  
  393.   // populate the second buffer
  394.   p_wave_hdr2^ := p_wave_hdr1^;              // copy most of the data
  395.   p_wave_hdr2^.lpData := pChar (p_buffer2);  // except the buffer address!
  396.  
  397.   case speed of
  398.     fast_smooth, fast_stepped:
  399.       begin
  400.       // fill in a single buffer that is repeated
  401.       if speed = fast_smooth
  402.       then samples := fill_single_sweep_bfr (p_buffer1, 1000)  // many frequencies
  403.       else samples := fill_single_sweep_bfr (p_buffer1, 20);   // just 20 frequencies
  404.       with p_wave_hdr1^ do
  405.         begin
  406.         dwBufferLength := 2*samples;              // convert samples to bytes
  407.         dwFlags := whdr_BeginLoop or whdr_EndLoop;
  408.         dwLoops := 65535;
  409.         end;
  410.       // prepare both headers but write just the first
  411.       waveOutPrepareHeader (hWave_out, p_wave_hdr1, SizeOf (TWaveHdr));
  412.       waveOutPrepareHeader (hWave_out, p_wave_hdr2, SizeOf (TWaveHdr));
  413.       waveOutWrite (hWave_out, p_wave_hdr1, SizeOf (TWaveHdr));
  414.       end;
  415.     slow, no_sweep:
  416.       begin
  417.         // compute number of chunks in the sweep, ensure it's at least two
  418.         // aim for about four different frequencies per second
  419.         chunks := trunc ((sweep_time * sample_rate) / (sample_rate div 4) + 0.999);
  420.         if chunks < 2 then chunks := 2;
  421.         buffer_fill := (trunc (sweep_time * 2.0 * sample_rate / chunks)) and $FFFFFFFE;
  422.         f_ratio := exp (ln (f_max/f_min) / (chunks-1));       // per step
  423.         f_step := (f_max + 0.01 - f_min) / (chunks-1);
  424.         f := f_min;
  425.         p_wave_hdr1^.dwBufferLength := buffer_fill;     // actual buffer sizes
  426.         p_wave_hdr2^.dwBufferLength := buffer_fill;
  427.         buffers_played := 0;
  428.         buffers_written := 0;
  429.         // now write the first two buffers into the wave output
  430.         waveOutPrepareHeader (hWave_out, p_wave_hdr1, SizeOf (TWaveHdr));
  431.         write_next_buffer (p_wave_hdr1);
  432.         waveOutPrepareHeader (hWave_out, p_wave_hdr2, SizeOf (TWaveHdr));
  433.         write_next_buffer (p_wave_hdr2);
  434.       end;
  435.   end;
  436. end;
  437.  
  438.  
  439. procedure TForm1.write_next_buffer (header: pWaveHdr);
  440. begin
  441.   if shutoff then Exit;
  442.   with header^ do
  443.     begin
  444.     // fill buffer with sinewave data, record the frequency in the user field
  445.     fill_buffer_with_sinewave (pBuffer (lpData), 0, dwBufferLength div 2);
  446.     dwUser := round (f);
  447.     end;
  448.   last_f := f;
  449.   // write the buffer and bump the number written
  450.   waveOutWrite (hWave_out, header, SizeOf (TWaveHdr));
  451.   Inc (buffers_written);
  452.   if speed = no_sweep
  453.   then
  454.     all_written := False
  455.   else
  456.     begin
  457.     if log_lin = linear
  458.     then f := f + f_step
  459.     else f := f * f_ratio;
  460.     // check to see if we've reached the maximum frequency
  461.     all_written := f > f_max;
  462.     end;
  463. end;
  464.  
  465.  
  466. procedure TForm1.mm_wom_done (var Msg: tMessage);
  467. // handle the wave out done message by writing the next buffer, if required
  468. var
  469.    free_header: pWaveHdr;
  470. begin
  471.   case speed of
  472.     fast_smooth, fast_stepped:
  473.       begin
  474.       // nothing to do
  475.       end;
  476.     slow, no_sweep:
  477.       begin
  478.       // note the fact that another buffer has been completed
  479.       Inc (buffers_played);
  480.       // point to wave header just completed, i.e. the next free buffer
  481.       free_header := pWaveHdr (msg.lParam);
  482.       if not shutoff then
  483.         begin
  484.         if (all_written) or (buffers_played >= buffers_written)
  485.         then
  486.           begin
  487.           // everything written has been played
  488.           shutoff := true;
  489.           sweep_running := false;
  490.           closing := false;         // say we're not closing just yet
  491.           end
  492.         else
  493.           begin
  494.           // make a note of the last frequency for the user
  495.           lblFnow.Caption := Format ('%.0f Hz', [last_f]);
  496.           // and write the next buffer, re-using the one just played
  497.           write_next_buffer (free_header);
  498.           end
  499.         end;
  500.       end;
  501.   end;
  502.   if shutoff then
  503.     begin
  504.     waveOutReset (hWave_out);
  505.     waveOutClose (hWave_out);
  506.     end;
  507. end;
  508.  
  509.  
  510. procedure TForm1.mm_wom_close (var Msg: tMessage);
  511. // handle the wave out close message, release the wave headers
  512. begin
  513.   waveOutUnprepareHeader (hWave_out, p_wave_hdr1, SizeOf (TWaveHdr));
  514.   waveOutUnprepareHeader (hWave_out, p_wave_hdr2, SizeOf (TWaveHdr));
  515.   p_wave_hdr1 := pWaveHdr (GlobalLock (hWave_hdr1));
  516.   if p_wave_hdr1 = nil then
  517.     ShowMessage ('Failed to re-lock buffer p_wave_hdr1!');
  518.   p_wave_hdr2 := pWaveHdr (GlobalLock (hWave_hdr2));
  519.   if p_wave_hdr2 = nil then
  520.     ShowMessage ('Failed to re-lock buffer p_wave_hdr2!');
  521.   lblFnow.Visible := False;
  522.   btnStart.Caption := 'Start';
  523.   hWave_out := 0;
  524.   closed := true;
  525.   if closing then Close;
  526. end;
  527.  
  528.  
  529. procedure TForm1.do_sine_table;
  530. var
  531.   i: 0..sine_table_samples - 1;
  532.   y, magnitude: extended;
  533. begin
  534.   if sine_table_done then Exit;     // nothing to do
  535.  
  536.   // convert dB to a mathematical fraction of full amplitude
  537.   case level of
  538.      dB0: magnitude := 1.0;
  539.      dB3: magnitude := 0.707;
  540.      dB6: magnitude := 0.5;
  541.      dB9: magnitude := 0.354;
  542.     dB12: magnitude := 0.25;
  543.     dB15: magnitude := 0.177;
  544.     dB18: magnitude := 0.125;
  545.     dB20: magnitude := 0.1;
  546.   else
  547.     magnitude := 0.25;   // should never be here, but just in case.....
  548.   end;
  549.  
  550.   // yes, I realise we could symmetry to reduce the number of computations
  551.   // required, but it really doesn't take that long.
  552.   for i := 0 to sine_table_samples - 1 do
  553.     begin
  554.     // Assume 16-bit audio goes from -32767..32767, avoids clipping.
  555.     // There are only 2^15 samples here, this simplfies the subsequent angle
  556.     // calculation but might restrict the dynamic range produced with noise
  557.     // sidebands.  However, in the quality of equipment likely to be
  558.     // encountered this won't matter.  You've got the source code, so
  559.     // you can alter this if you like.
  560.     y := round (magnitude * (32767.0 * sin (2.0* i * Pi / sine_table_samples)));
  561.     sine_table^ [i] := round (y);
  562.     end;
  563.  
  564.   sine_table_done := true;
  565. end;
  566.  
  567.  
  568. procedure TForm1.fill_buffer_with_sinewave (bfr: pBuffer;  index, samples: integer);
  569. const
  570.   fract_bits = 15;
  571. var
  572.   sample: integer;
  573.   d_angle: integer;      // 32-bit number, with 14 fractional bits, i.e. 17.15
  574.   max_angle: integer;
  575.   w: audio_sample;
  576. begin
  577.   // compute the angular step per sample corresponding to the desired frequency
  578.   d_angle := round ((sine_table_samples shl fract_bits) * f / sample_rate);
  579.   // this is the maximum number of samples in the sine table
  580.   max_angle := (sine_table_samples shl fract_bits) - 1;
  581.   for sample := 0 to samples - 1 do
  582.     begin
  583.     w := sine_table^ [angle shr fract_bits];   // get current sine value
  584.     bfr^ [index] := w;                         // store it in the caller's buffer
  585.     Inc (index);                               // bump the buffer pointer
  586.     Inc (angle, d_angle);                      // bump the angle
  587.     angle := angle and max_angle;              // wrap to 360 degrees
  588.     end;
  589. end;
  590.  
  591.  
  592. function TForm1.fill_single_sweep_bfr (bfr: pBuffer;  num_freqs: integer): integer;
  593. // This procedure fills a single buffer with a frequency sweep.
  594. // To allow for oscilloscope retrace and retrigger time, the buffer
  595. // is prefixed with about 25% duration of silence.
  596. // Both log and linear sweeps can be provided
  597. // resturn the number of samples in the buffer
  598. var
  599.   sample, chunk_samples, retrace_steps: integer;
  600.   i, n_freq: integer;
  601. begin
  602.   // for linear sweep, compute the frequency step
  603.   f_step := (f_max + 0.01 - f_min) / (num_freqs-1);
  604.  
  605.   // for log sweep, compute the frequency ratio per step
  606.   f_ratio := exp (ln (f_max/f_min) / (num_freqs-1));
  607.  
  608.   retrace_steps := num_freqs div 3;    {allow about 25% retrace time}
  609.   chunk_samples := buffer_bytes div (2 * (num_freqs + retrace_steps));
  610.   sample := 0;
  611.   angle := 0;
  612.   f := f_min;
  613.  
  614.   // for all buffer chunks, including silence
  615.   for n_freq := 1 to retrace_steps + num_freqs do
  616.     begin
  617.     if n_freq <= retrace_steps
  618.     then
  619.       for i := 0 to chunk_samples - 1 do    // over the entire chunk
  620.         begin
  621.         bfr^ [sample] := 0;                 // insert silence
  622.         Inc (sample);                       // point to next sample
  623.         end
  624.     else
  625.       begin
  626.       // stuff sinewave into this chunk
  627.       fill_buffer_with_sinewave (bfr, sample, chunk_samples);
  628.       Inc (sample, chunk_samples);
  629.       // compute next frequency according to the sweep mode
  630.       if log_lin = linear
  631.       then f := f + f_step
  632.       else f := f * f_ratio;
  633.       end;
  634.     end;
  635.  
  636.   Result := sample;
  637. end;
  638.  
  639.  
  640. procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
  641. begin
  642.   stop_sweep;
  643.   shutoff := true;
  644. end;
  645.  
  646.  
  647. end.
  648.